
using System;
using System.Collections;
using System.Text;
using GeoFramework;

namespace GeoFramework.Gps.Nmea
{
	/// <summary>
	/// Represents a $GPGSV sentence describing the location and signal strength of GPS
	/// satellites.
	/// </summary>
	/// <remarks>This sentence is used to determine the location of GPS satellites relative
	/// to the current location, as well as to indicate the strength of a satellite's radio
	/// signal.  </remarks>
	public sealed class GpgsvSentence : NmeaSentence //, IDisposable 
	{
		private int pTotalMessageCount;
		private int pCurrentMessageNumber;
		private int pSatellitesInView;
		private SatelliteCollection pSatellites;

		#region Constructors

		public GpgsvSentence(string sentence) : base(sentence)
		{
			// Is the checksum valid?
			if (!IsValid && !IsPartialSentenceParsingAllowed)
			{
				pSatellites = SatelliteCollection.Empty;
				// No.  Do no parsing
				return;
			}

			// Example (signal not acquired): $GPGSV,1,1,01,21,00,000,*4B
			// Example (signal acquired): $GPGSV,3,1,10,20,78,331,45,01,59,235,47,22,41,069,,13,32,252,45*70
			try
			{
				// Get the total message count
				if(WordCount > 1 && Words[1].Length != 0)
						pTotalMessageCount = int.Parse(Words[1], NmeaCultureInfo);

				// Get the current message number
				if(WordCount > 2 && Words[2].Length != 0)
						pCurrentMessageNumber = int.Parse(Words[2], NmeaCultureInfo);
	
				// Get the total message count
				if(WordCount > 3 && Words[3].Length != 0)
						pSatellitesInView = int.Parse(Words[3], NmeaCultureInfo);

				pSatellites = new SatelliteCollection();

				// Now process each satellite
				for(int index = 0; index < 6; index++)
				{
					int CurrentWordIndex = index * 4 + 4;
					// Are we past the length of words?
					if(CurrentWordIndex > WordCount - 1)
						break;
					// No.  Get the unique code for the satellite
					if(Words[CurrentWordIndex].Length == 0)
						continue;
					int PseudorandomNumber = int.Parse(Words[CurrentWordIndex], NmeaCultureInfo);
					Elevation NewElevation;
					Azimuth NewAzimuth;
					SignalToNoiseRatio NewSignalToNoiseRatio;

					// Update the elevation
					if(WordCount > CurrentWordIndex + 1 && Words[CurrentWordIndex + 1].Length != 0)
						NewElevation = Elevation.Parse(Words[CurrentWordIndex + 1], NmeaCultureInfo);
					else
						NewElevation = Elevation.Empty;
					// Update the azimuth
					if(WordCount > CurrentWordIndex + 2 && Words[CurrentWordIndex + 2].Length != 0)
						NewAzimuth = Azimuth.Parse(Words[CurrentWordIndex + 2], NmeaCultureInfo);
					else
						NewAzimuth = Azimuth.Empty;
					// Update the signal strength
					if(WordCount > CurrentWordIndex + 3 && Words[CurrentWordIndex + 3].Length != 0)
						NewSignalToNoiseRatio = SignalToNoiseRatio.Parse(Words[CurrentWordIndex + 3], NmeaCultureInfo);			
					else
						NewSignalToNoiseRatio = SignalToNoiseRatio.Empty;
					// Add the satellite to the collection
					pSatellites.Add(
						new Satellite(PseudorandomNumber,
						NewAzimuth, NewElevation, NewSignalToNoiseRatio));
				}
			}
			catch(Exception ex)
			{
				throw new NmeaSentenceException(this, "A $GPGSV sentence contained unrecognizable data.", ex);
			}
		}

		public GpgsvSentence(byte[] bytes) : this(ASCIIEncoding.ASCII.GetString(bytes, 0, bytes.Length))
		{
		}

		public GpgsvSentence(int totalMessageCount, int currentMessageNumber, 
			int satellitesInView, SatelliteCollection satellites)
		{
			pTotalMessageCount = totalMessageCount;
			pCurrentMessageNumber = currentMessageNumber;

			pSatellites = SatelliteCollection.ReadOnly(satellites);

			pSatellitesInView = satellitesInView;
			// Build the sentence from the position information
			StringBuilder Sentence = new StringBuilder();
			Sentence.Append("$GPGSV,");
			// Append the total message count
			Sentence.Append(pTotalMessageCount);
			Sentence.Append(",");
			// Append the current message number
			Sentence.Append(pCurrentMessageNumber);
			Sentence.Append(",");
			// Append the satellites in view
			Sentence.Append(pSatellitesInView);
			// Append the satellite collection
			if(Satellites != null)
			{
				foreach(Satellite satellite in Satellites)
				{
					// Append PRN
					Sentence.Append(",");
					Sentence.Append(satellite.PseudorandomNumber.ToString().PadLeft(2, '0'));
					Sentence.Append(",");
					Sentence.Append(satellite.Elevation.ToString("dd"));
					Sentence.Append(",");
					Sentence.Append(satellite.Azimuth.ToString("ddd"));
					Sentence.Append(",");
					Sentence.Append(satellite.SignalToNoiseRatio.Value.ToString().PadLeft(2, '0'));
				}
			}
			// Set the sentence
			SetSentence(Sentence.ToString());
			SetChecksum();
		}

		#endregion

		#region Static Members

		/// <summary>Returns a new instance by interpreting the specified string.</summary>
		public static GpgsvSentence Parse(string sentence)
		{
			return new GpgsvSentence(sentence);
		}

		/// <summary>
		/// Returns a collection of $GPGSV sentences fully describing the specified
		/// collection of satellites.
		/// </summary>
		public static ArrayList FromSatellites(SatelliteCollection satellites)
		{
			// Divide the collecting into 4-satellite chunks
			int TotalMessages = (int)satellites.Count / 4;
			ArrayList Result = new ArrayList(TotalMessages);
			// And populate each member of the array
			for(int MessageNumber = 1; MessageNumber <= TotalMessages; MessageNumber++)
			{
				// Make a collection with just the satellites we want
				SatelliteCollection SubSet = new SatelliteCollection();
				for(int count = 0; count < 4; count++)
				{
					// Calculate the satellite to add
					int SatelliteIndex = (MessageNumber - 1) * 4 + count;
					// Are we at the end of the collection?
					if(SatelliteIndex >= satellites.Count)
						// Yes.  Stop adding
						break;
					// Copy the satellite in
					SubSet.Add((Satellite)satellites[SatelliteIndex]);
				}
				// Now make the sentence
				Result.Add(new GpgsvSentence(TotalMessages, MessageNumber, satellites.Count, SubSet));
			}
			
			// return all sentences
			return Result;
		}

		/// <summary>Represents a $GPGSV sentence with no value.</summary>
		public static readonly GpgsvSentence Empty = new GpgsvSentence(0, 0, 0, new SatelliteCollection(0));

		#endregion

		#region Public Members

		/// <summary>
		/// Returns a collection of <strong>Satellite</strong> objects describing current
		/// satellite information.
		/// </summary>
		public SatelliteCollection Satellites
		{
			get
			{
				return pSatellites;
			}
		}

		/// <summary>Returns the total number of $GPGSV sentence in a sequence.</summary>
		public int TotalMessageCount
		{
			get
			{
				return pTotalMessageCount;
			}
		}

		/// <summary>
		/// Returns the current message index when the sentence is one of several
		/// messages.
		/// </summary>
		public int CurrentMessageNumber
		{
			get
			{
				return pCurrentMessageNumber;
			}
		}

		/// <summary>
		/// Returns the number of satellites whose signals are detected by the GPS
		/// device.
		/// </summary>
		public int SatellitesInView
		{
			get
			{
				return pSatellitesInView;
			}
		}

		/// <summary>
		/// Indicates that this sentence is supported by GPS.NET.
		/// </summary>
		public override bool IsSupported
		{
			get
			{
				return true;
			}
		}

		#endregion

    }
}
